/*
 * Copyright (C) 2002-2004, Inversoft Corporation, All Rights Reserved.
 */
package org.emergent.antbite.savant.ant.taskdefs;


import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.MacroInstance;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Mapper;
import org.apache.tools.ant.util.FileNameMapper;
import org.apache.tools.ant.util.IdentityMapper;


/**
 * Run an Ant task several times, setting a param from a tokenized list
 * or fileset each time.
 *
 *    target - target to be called, like antcall. Required.
 *    property - name of property to set to value on each call.  Required.
 *    mappedproperty - name of property to be set to mapped value on each call.
 *    delims - delimiters for tokenizing values.  Defaults to "\t\n, ".
 *    list - a delimited list of values.  target is called once with each of
 *           these values setting property.
 *    fileset - target is called once for each file, with property set
 *              to the full path to the file.
 *    dirset - target is called once for each file, with property set
 *             to the full path to the directory.
 *
 * An optional mapper is allowed, the default is Identity.
 * If specified, then target is only called for those values or files which
 * match the mapper, and mappedproperty is set to the result "to" value on
 * each call.  Works for both filesets and values.
 *
 *
 * See the AntCall task.
 */
public class MacroForeachTask extends Task {

    private String macroName;
    private String delims = ", \t\n";
    private String property = null;
    private String mappedProperty = null;
    private String list = null;
    private final List filesets = new ArrayList();
    private final List dirsets = new ArrayList();
    private final List params = new ArrayList();
    private Mapper mapperElement;


    public void setMacro(String macroName) {
        this.macroName = macroName;
    }

    /**
     * Sets the property to be set with each call.
     */
    public void setProperty(String property) {
        this.property = property;
    }

    /**
     * Sets the property to be set with each call.
     */
    public void setMappedproperty(String property) {
        this.mappedProperty = property;
    }

    /**
     * Sets the list of values to iterate over
     */
    public void setList(String list) {
        this.list = list;
    }

    /**
     * Sets tokens.
     */
    public void setDelims(String delims) {
        this.delims = delims;
    }

    /**
     * Adds a set of files (nested fileset attribute).
     */
    public void addFileset(FileSet set) {
        filesets.add(set);
    }

    /**
     * Adds a new nested dirset
     */
    public void addDirset(FileSet set) {
        dirsets.add(set);
    }

    /**
     * Adds a new parameter that is passed to the macro.
     */
    public void addParam(Param param) {
        params.add(param);
    }

    /**
     * Creates a new nested mapper
     */
    public Mapper createMapper() throws BuildException {
        if (mapperElement != null) {
            throw new BuildException("Cannot define more than one mapper",
                getLocation());
        } else {
            mapperElement = new Mapper(getProject());
            return mapperElement;
        }
    }

    /**
     * Do the looping
     */
    public void execute() throws BuildException {
        validate();

        // Set up mapper
        FileNameMapper mapper = null;
        if (mapperElement != null) {
            mapper = mapperElement.getImplementation();
        } else {
            mapper = new IdentityMapper();
        }

        // Loop for tokenized values
        if (list != null) {
            for (StringTokenizer st = new StringTokenizer(list, delims);
                    st.hasMoreTokens(); ) {
                String val = st.nextToken();
                String[] val2 = mapper.mapFileName(val);

                callTarget(val, val2);
            }
        }

        // loop for fileset
        if (filesets.size() != 0) {

            // Walk thru the filesets
            for (Iterator sets = filesets.iterator(); sets.hasNext(); ) {
                FileSet fs = (FileSet) sets.next();
                File fromDir = fs.getDir(getProject());
                ArrayList fromFiles = new ArrayList();

                fromFiles.addAll(
                    Arrays.asList(fs.getDirectoryScanner(getProject()).getIncludedFiles()));

                for (Iterator files = fromFiles.iterator(); files.hasNext(); ) {
                    String f = (String) files.next();
                    String val = new File(fromDir, f).getPath();
                    String[] val2 = mapper.mapFileName(val);

                    callTarget(val, val2);
                }
            }
        }

        // loop for dirset
        if (dirsets.size() != 0) {

            // Walk thru the dirsets
            for (Iterator sets = dirsets.iterator(); sets.hasNext(); ) {
                FileSet fs = (FileSet) sets.next();
                File fromDir = fs.getDir(getProject());
                ArrayList fromFiles = new ArrayList();

                fromFiles.addAll(
                    Arrays.asList(fs.getDirectoryScanner(getProject()).getIncludedDirectories()));

                for (Iterator files = fromFiles.iterator(); files.hasNext(); ) {
                    String f = (String) files.next();
                    String val = new File(fromDir, f).getPath();
                    String[] val2 = mapper.mapFileName(val);

                    callTarget(val, val2);
                }
            }
        }
    }

    private void validate() {
        if (property == null && mappedProperty == null) {
            throw new BuildException( "Specify a property or mappedproperty name.",
                getLocation());
        }

        if (!(list == null ^ (filesets.size() == 0 && dirsets.size() == 0))) {
            throw new BuildException("Specify either list or a fileset.", getLocation());
        }
    }

    /**
     * Calls the ant target that the loop is setup to call
     */
    private void callTarget(String val, String[] val2) {
        if (val2 == null || val2.length == 0) {
            log("Skipping because no mapped property", Project.MSG_DEBUG);
            return;
        }

        Task task = getProject().createTask(macroName);
        if (!(task instanceof MacroInstance)) {
            throw new BuildException("Only macros can be used with the marcoforeach task");
        }

        for (int j = 0; j < val2.length; ++j) {
            MacroInstance macro = (MacroInstance) task;
            if (property != null) {
                macro.setDynamicAttribute(property, val);
            }

            if (mappedProperty != null) {
                macro.setDynamicAttribute(mappedProperty, val2[j]);
            }

            // Add the extra parameters
            for (Iterator iter = params.iterator(); iter.hasNext(); ) {
                Param param = (Param) iter.next();
                macro.setDynamicAttribute(param.getName(), param.getValue());
            }

            macro.perform();
        }
    }


    /**
     * Cheesy inner class for supporting parameters to marcos.
     */
    public static class Param {
        private String name;
        private String value;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }
    }
}